import Vue from 'vue'
import util from './util.js'
import bleUtils from './BleUtils.js'
import {
	getLockUpgradeMessage
} from '@/api/lock.js'
import crc from './crc32.js';
const osName = Vue.prototype.$sysInfo.osName;
class Upgrage {
	lockNo = ''; // 门锁ID
	mobile = ''; // 用户手机号
	mac = '';
	formatMac = '';
	downLoadPackUrl = '';
	cmdType = 1; // DFU的select命令的type参数  1:表示Command object(发送到Control point) 2:Data object(发送到Data point)
	totalPage = 0; // bin_data的数据/4096(4KB)+1   totalPage:表示bin_data 分成4KB 一个page 总共有多少page
	curPage = 0; // 当前已经发送到第几页
	bin_offset = 0; // bin数据包的发送偏移量
	binByteLength = 0; // bin数据包总字节数
	dat_data = ''; // bat文件数据
	bin_data = ''; // bin文件数据
	cmdArr = [];
	progressFunc = null; // 进度调用函数
	successFunc = null; // 升级成功函数
	errFunc = null; // 失败的回调
	mtuSize = 20; // 蓝牙MTU传输单元字节数
	curWriteBin = ''; // 当前正在写的bin数据
	remainWriteBin = ''; // 有时传输4096个字节 设备响应通常没有4096个字节 校验不通过 还需要写的字节数
	isBreakPointResum = false; // 是否是断点续传
	isReciveBleData = false;
	constructor({
		downLoadPackUrl,
		lockNo,
		mobile,
		mac,
		progressFunc,
		successFunc,
		errFunc
	}) {
		this.downLoadPackUrl = downLoadPackUrl;
		this.progressFunc = progressFunc;
		this.successFunc = successFunc;
		this.errFunc = errFunc;
		this.lockNo = lockNo;
		this.mobile = mobile;
		this.mac = mac;
		this.formatMac = util.strInsert(mac, 2);
	}
	async startUpgrade() {
		try {
			await this.downFirmware();
			await this.unzip(this.lockNo);
			await this.getUpgradeCmdMsg();
			this.upgradeLock(); // 开始升级
		} catch (e) {
			this.errFunc(e)
		}
	}
	downFirmware() {
		this.progressFunc('下载升级包');
		const filemgr = wx.getFileSystemManager();
		const targetPath = wx.env.USER_DATA_PATH;
		return new Promise((resolve, reject) => {
			wx.downloadFile({
				url: this.downLoadPackUrl,
				success: (res) => {
					console.log('下载res', res)
					filemgr.saveFile({
						tempFilePath: res.tempFilePath,
						// filePath: targetPath + "/firmware.zip",
						filePath: targetPath + "/" + this.lockNo + ".zip",
						success: (result) => {
							resolve();
						},
						fail: (e) => {
							reject('保存文件失败!')
						}
					})
				}
			})
		})
	}
	unzip(lockNo) {
		this.progressFunc('解压升级包');
		const filemgr = wx.getFileSystemManager()
		const targetPath = wx.env.USER_DATA_PATH
		console.log('路径' + targetPath + "/" + lockNo + ".zip")
		return new Promise((resolve, reject) => {
			filemgr.unzip({
				zipFilePath: targetPath + "/" + lockNo + ".zip",
				targetPath: targetPath,
				success: (unzipRes) => {
					console.log("解压成功:" + JSON.stringify(unzipRes))
					filemgr.readFile({
						filePath: targetPath + '/manifest.json',
						encoding: 'binary',
						complete: (readFileRes) => {
							console.log('读取压缩包文件返回 ', readFileRes);
							const data = JSON.parse(readFileRes.data);
							const application = data.manifest.application;
							console.log("dat_file文件路径为", targetPath + '/' + application.dat_file)
							console.log("bin_file文件路径为", targetPath + '/' + application.bin_file)
							const readDatData = new Promise((resolve, reject) => {
								//bat文件的解析
								filemgr.readFile({
									filePath: targetPath + '/' + application.dat_file,
									complete: (datRes) => {
										const dat_data = util.ab2hex(datRes.data)
										this.dat_data = datRes.data
										console.log('dat data=>', dat_data, ' byteLength==>', datRes.data.byteLength)
										resolve();
									}
								})
							})
							const readBinData = new Promise((resolve, reject) => {
								//bin文件的解析
								filemgr.readFile({
									filePath: targetPath + '/' + application.bin_file,
									complete: (binRes) => {
										this.curPage = 0; // 初始化
										this.bin_offset = 0;
										this.bin_data = binRes.data;
										this.binByteLength = this.bin_data.byteLength;
										this.totalPage = Math.ceil(this.binByteLength / 4096);
										console.log('bin byteLength=>', this.binByteLength);
										resolve();
									}
								})
							})
							Promise.all([readDatData, readBinData]).then(() => {
								resolve();
							}).catch((err) => {
								reject('解压文件失败!');
							})
						}
					})
				},
				fail: function(unzipRes) {
					console.log('解压失败' + unzipRes.errMsg)
					reject('解压失败' + unzipRes.errMsg)
				}
			})
		})

	}
	getUpgradeCmdMsg() {
		this.progressFunc('获取升级报文');
		return new Promise(async (resolve, reject) => {
			let data = {
				lockNo: this.lockNo, //this.upgradeList[this.index].lockNo,
				mobile: this.mobile,
			}
			const res = await getLockUpgradeMessage(data)
			if (res && res.statecode == 0) {
				console.log('升级报文', res);
				let taskMessage = res.data.taskMessage.replace(" ", "")
				let cmd_buffer = util.myStringtoHex(taskMessage);
				console.log('报文字符串转十六进制后的报文长度: ' + cmd_buffer.byteLength, ' osName-->', osName);
				if (osName == 'ios') {
					this.mtuSize = 128; // 如果是IOS系统 蓝牙传输单元可以设置为128个字节
				} else {
					this.mtuSize = 20;
				}
				console.log('mtuSize===>', this.mtuSize);
				this.cmdArr = util.strSplitToArr(cmd_buffer, this.mtuSize);
				resolve();
			} else {
				reject('获取报文失败')
			}
		})
	}
	async upgradeLock() {
		try {
			this.isBreakPointResum = false;
			this.isReciveBleData = false;
			this.progressFunc('开始升级');
			await bleUtils.doAuthorize('scope.userLocation');
			await bleUtils.openBluetoothAdapter();
			await bleUtils.waitBleConnAndNotify(this.mac, this.formatMac);
			bleUtils.onBLECharacteristicValueChange((res) => {
				this.receiveBleData(res)
			});
			let cmdData = '';
			// 循环发送多条命令
			do {
				cmdData = this.cmdArr.splice(0, 1)[0];
				const unit8Array_data = new Uint8Array(cmdData);
				const buffer = unit8Array_data.buffer;
				await bleUtils.writeBLECharacteristicValue(buffer);
			} while (cmdData);
		} catch (e) {
			console.log('upgradeLock err', e)
			let msg = '';
			if (e && e.errCode) {
				switch (e.errCode) {
					case 10001:
						msg = '请确保蓝牙已开启'
						break;
					case 99991:
						msg = '请授权定位服务'
						break;
					default:
						msg = '未知错误'
						break;
				}
			} else {
				msg = '设置DFU失败!';
			}
			this.errFunc(msg)
		}
	}
	async receiveBleData(res) {
		if (this.isReciveBleData) {
			return;
		}
		this.isReciveBleData = true;
		try {
			await bleUtils.closeBLEConnection();
			/**
			 * 为了兼容红米手机  断开连接后 必须关闭蓝牙设备器   然后 等待200毫秒 再打开蓝牙适配器 然后获取权限 然后 再连接 
			 * 红米等其他安卓真垃圾
			 */
			await bleUtils.closeBluetoothAdapter();
			console.log('处于DFU模式');
			setTimeout(async () => {
				await bleUtils.openBluetoothAdapter();
				// await bleUtils.doAuthorize('scope.userLocation');
				await bleUtils.waitBleConnAndNotify(this.mac, this.formatMac, true);
				bleUtils.onBLECharacteristicValueChange((res) => {
					this.dataEventHandler(res)
				});
				const buffer = new ArrayBuffer(2);
				const dataView = new DataView(buffer);
				/**
				 * 发送一条06命令 也就是select命令 
				 * select命令需要传递Type参数  01:Command object  02:Data object
				 */
				dataView.setUint8(0, 0x06);
				dataView.setUint8(1, 0x01);
				this.cmdType = 1;
				console.log('write data to 90001')
				bleUtils.writeTo90001(buffer);
			}, 2000)
		} catch (e) {
			console.log('receiveBleData err', e)
			this.errFunc('DFU模式开启失败!')
		}
	}
	async dataEventHandler(res) {
		try {
			console.log('receive DFU res ', JSON.stringify(res))
			const response = util.hexToBytes(res);
			const parseResp = bleUtils.parseResponse(response);
			console.log('parseResp===> ', JSON.stringify(parseResp));
			const respOpCode = parseResp.responseOpCode;
			switch (respOpCode) {
				case 0x01: // create
					console.log('-----case 0x01 create')
					if (this.cmdType == 1) {
						const datSize = this.dat_data.byteLength;
						console.log('case 0x01 send dat data to 90002')
						this.writeDatData(datSize, 0); // 向90002写入数据
					} else {
						if (this.remainWriteBin) {
							this.writeBinData(this.remainWriteBin, this.remainWriteBin.byteLength, 0) // 向90002写入没到4096个字节的剩余字节数据
						} else {
							// 发送bin数据,没接收4KB数据,会回复一次CRC校验值,直至整个新固件image接收完毕
							if (this.isBreakPointResum) {
								const start = Math.floor(this.bin_offset / 4096) * 4096;
								console.log('start page -->', start);
								const cutSize = this.binByteLength - start > 4096 ? 4096 : this.binByteLength - start;
								const buffer = this.curWriteBin = this.bin_data.slice(start, start + cutSize); // 4KB数据
								const binSize = buffer.byteLength;
								console.log('case 0x01 create send bin ', binSize, ' data to 90002')
								this.writeBinData(buffer, binSize, 0); // 向90002写入数据
							} else {
								const cutSize = this.binByteLength - this.bin_offset > 4096 ? 4096 : this.binByteLength - this.bin_offset;
								const buffer = this.curWriteBin = this.bin_data.slice(this.bin_offset, this.bin_offset + cutSize); // 4KB数据
								const binSize = buffer.byteLength;
								console.log('case 0x01 create send bin ', binSize, ' data to 90002')
								this.writeBinData(buffer, binSize, 0); // 向90002写入数据
							}
						}
					}
					break;
				case 0x02: // set PRN					
					console.log('-----case 0x02 SET_PRN')
					break;
				case 0x03: // CALCULATE CHECKSUM
					console.log('-----case 0x03 CALCULATE CHECKSUM')
					if (this.cmdType == 1) {
						const buffer = new ArrayBuffer(1);
						const dataView = new DataView(buffer);
						dataView.setUint8(0, 0x04); // 写入通道指令
						console.log('----------case 0x03 send 04 ', util.arrayBuffer2Hex(buffer), ' to 90001 cmdType is 1')
						bleUtils.writeTo90001(buffer); // 想90001写入通道指令数据
					} else {
						this.bin_offset = parseResp.data.offset;
						this.curPage = Math.ceil(this.bin_offset / 4096); // 看下已经发到第几页														
						console.log('curPage=>', this.curPage, ' bin_offset=>', this.bin_offset, ' binByteLength=>', this.binByteLength)
						// // 下面开始校验crc
						const crcStarIdx = this.isBreakPointResum ? this.bin_offset : this.curPage * 4096;
						console.log('resp crc32->', parseResp.data.crc32, ' crc32->', crc.crc32(this.bin_data.slice(0, crcStarIdx)))
						if (parseResp.data.crc32 != crc.crc32(this.bin_data.slice(0, crcStarIdx))) {
							// const remainLength = this.curPage != this.totalPage ? this.curPage * 4096 - this.bin_offset : this.binByteLength - this.bin_offset;
							const remainLength = this.curPage * 4096 - this.bin_offset;
							if (remainLength > 0) {
								console.log('remainLength---->', remainLength)
								this.remainWriteBin = this.curWriteBin.slice(this.bin_offset);
								if (remainLength < 0x1000) {
									this.sendCreatePackage(bleUtils.formater4Length(remainLength));
								} else {
									this.sendCreatePackage(bleUtils.formater4Length(0x1000));
								}
								return;
							}
						} else {
							this.remainWriteBin = '';
						}
						const percent = ((this.bin_offset / this.binByteLength) * 100).toFixed(2);
						let msg = '已发送' + percent + '%';
						this.progressFunc(msg);
						const buffer = new ArrayBuffer(1);
						const dataView = new DataView(buffer);
						dataView.setUint8(0, 0x04); // 发送执行指令
						console.log('----------case 0x03 CALCULATE CHECKSUM write 04 ', util.arrayBuffer2Hex(buffer), ' to 90001')
						bleUtils.writeTo90001(buffer); // 向90001发送数据
					}
					break;
				case 0x04: // execute
					console.log('-----case 0x04 execute')
					if (this.cmdType == 1) {
						const buffer = new ArrayBuffer(2);
						const dataView = new DataView(buffer);
						this.cmdType = 2; // 2表示开始发送升级包的bin包数据了
						dataView.setUint8(0, 0x06);
						dataView.setUint8(1, 0x02);
						console.log('-----case 0x04 execute send 02 ', util.arrayBuffer2Hex(buffer), ' to 90001 cmdType is 2')
						bleUtils.writeTo90001(buffer); // 向90001写入通道指令数据
					} else {
						if (this.curPage != this.totalPage) { // 还没发送完
							const remainLength = this.bin_data.byteLength - this.bin_offset;
							console.log('校验数据中:' + this.curPage + '/' + this.totalPage, ' 剩余字节数:', remainLength)
							if (remainLength < 0x1000) {
								this.sendCreatePackage(bleUtils.formater4Length(remainLength));
							} else {
								this.sendCreatePackage(bleUtils.formater4Length(0x1000));
							}
						} else { // 到这里就算升级成功了
							// 将一些参数恢复至默认值
							this.cmdType = 1;
							this.successFunc();
						}
					}
					break;
				case 0x06: // select
					const respOffset = parseResp.data.offset;
					const respCrc32 = parseResp.data.crc32;
					if (this.cmdType == 1) { // 如果cmdType是1 表示是Control point的操作返回
						// 如果返回的数据偏移量=要发送的dat包的字节长度 那么说明bat包已经发送成功了
						// 直接发送执行命令
						if (respOffset == this.dat_data.byteLength) {
							const buffer = new ArrayBuffer(1);
							const dataView = new DataView(buffer);
							dataView.setUint8(0, 0x04);
							console.log('-----case 0x06 select send 04 ', util.arrayBuffer2Hex(buffer), ' to 90001 cmdType=1')
							await bleUtils.writeTo90001(buffer); // 向9001发送04执行命令
						} else { // 发送创建命令
							const buffer = new ArrayBuffer(6);
							const dataView = new DataView(buffer);
							dataView.setUint8(0, 0x01); // create命令
							dataView.setUint8(1, 0x01); // create命令的types参数 1:command object 2:data object
							dataView.setUint8(2, 0x8d);
							dataView.setUint8(3, 0x0);
							dataView.setUint8(4, 0x0);
							dataView.setUint8(5, 0x0);
							console.log('-----case 0x06 select send 01 ', util.arrayBuffer2Hex(buffer), ' to 90001 cmdType=1')
							await bleUtils.writeTo90001(buffer);
						}
					} else {
						console.log('-----断点续传 case 0x06 select respOffset--->', respOffset)
						this.isBreakPointResum = false;
						// if (respOffset > 0 && respOffset < this.bin_data.byteLength) {
						// 	this.bin_offset = parseResp.data.offset; // 看下已经发到第几页							
						// 	this.isBreakPointResum = true;		
						// 	this.sendCreatePackage(bleUtils.formater4Length(0x1000));
						// 	return;
						// }
						/**
						 * 打包的版本 没有使用断点续传  直接每次重新传
						 */
						if (respOffset == 0 || respOffset > 0) {
							this.sendCreatePackage(bleUtils.formater4Length(0x1000));
							return;
						}
						// 直接发送执行命令
						const buffer = new ArrayBuffer(1);
						const dataView = new DataView(buffer);
						dataView.setUint8(0, 0x04);
						console.log('-----case 0x06 select send 04 to 90001')
						await bleUtils.writeTo90001(buffer); // 向9001发送04执行命令							
					}
					break;
				default:
					this.errFunc('未知的操作Code:' + respOpCode)
					break;
			}
		} catch (e) {
			const errMsg = '';
			if (typeof e == 'object') {
				console.log('dataEventHandler err', e)
			} else {
				console.log('dataEventHandler err', e)
			}
			this.errFunc('操作中心返回失败!')
		}
	}
	// 写入升级包的dat_data
	async writeDatData(datSize, offset) {
		do {
			const step = offset + this.mtuSize > datSize ? datSize - offset : this.mtuSize;
			console.log('offset', offset, 'step', step, 'datSize', datSize)
			const uint8Array = new Uint8Array(this.dat_data, offset, step);
			let hex = "";
			for (let i in uint8Array) {
				const num = uint8Array[i];
				if (num < 16) {
					hex += '0'
				}
				hex += num.toString(16)
			}
			// console.log("--->write dat data:" + hex);
			const writeBuffer = util.bufferFromHex(hex);
			await bleUtils.writeTo90002(writeBuffer); // 向90002写入数据		
			offset = offset + step;
		} while (offset < datSize)
		const buffer = new ArrayBuffer(1);
		const dataView = new DataView(buffer);
		dataView.setUint8(0, 0x03); // 写入通道指令
		bleUtils.writeTo90001(buffer); // 向90001写入数据
	}
	// 写入bin升级包数据
	async writeBinData(pack, binSize, offset) {
		do {
			const step = offset + this.mtuSize > binSize ? binSize - offset : this.mtuSize;
			// console.log('offset', offset, 'step', step, 'binSize', binSize)
			const uint8Array = new Uint8Array(pack, offset, step);
			let hex = "";
			for (let i in uint8Array) {
				const num = uint8Array[i];
				if (num < 16) {
					hex += '0'
				}
				hex += num.toString(16)
			}
			// console.log("--->write bin data:" + hex);
			const writeBuffer = util.bufferFromHex(hex);
			await bleUtils.writeTo90002(writeBuffer); // 向90002写入数据		
			offset = offset + step;
		} while (offset < binSize)
		// this.bin_offset += binSize;
		console.log('==================4k is over==================');
		const buffer = new ArrayBuffer(1);
		const dataView = new DataView(buffer);
		dataView.setUint8(0, 0x03); // 写入通道指令
		console.log('==================send 0x03==================');
		bleUtils.writeTo90001(buffer); // 向90001写入数据
	}
	// 发送创建命令
	sendCreatePackage(size) {
		const buffer = new ArrayBuffer(6)
		const dataView = new DataView(buffer)
		//写入通道指令 
		dataView.setUint8(0, 0x01)
		dataView.setUint8(1, 0x02)
		dataView.setUint8(2, size[0])
		dataView.setUint8(3, size[1])
		dataView.setUint8(4, 0x0)
		dataView.setUint8(5, 0x0)
		console.log("--->write create data: ", util.arrayBuffer2Hex(buffer));
		return bleUtils.writeTo90001(buffer);
	}
}
export default Upgrage;